// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2009 Guillaume Saupin <guillaume.saupin@cea.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_SKYLINEPRODUCT_H
#define EIGEN_SKYLINEPRODUCT_H

namespace Eigen {

template<typename Lhs, typename Rhs, int ProductMode>
struct SkylineProductReturnType
{
	typedef const typename internal::nested_eval<Lhs, Rhs::RowsAtCompileTime>::type LhsNested;
	typedef const typename internal::nested_eval<Rhs, Lhs::RowsAtCompileTime>::type RhsNested;

	typedef SkylineProduct<LhsNested, RhsNested, ProductMode> Type;
};

template<typename LhsNested, typename RhsNested, int ProductMode>
struct internal::traits<SkylineProduct<LhsNested, RhsNested, ProductMode>>
{
	// clean the nested types:
	typedef typename internal::remove_all<LhsNested>::type _LhsNested;
	typedef typename internal::remove_all<RhsNested>::type _RhsNested;
	typedef typename _LhsNested::Scalar Scalar;

	enum
	{
		LhsCoeffReadCost = _LhsNested::CoeffReadCost,
		RhsCoeffReadCost = _RhsNested::CoeffReadCost,
		LhsFlags = _LhsNested::Flags,
		RhsFlags = _RhsNested::Flags,

		RowsAtCompileTime = _LhsNested::RowsAtCompileTime,
		ColsAtCompileTime = _RhsNested::ColsAtCompileTime,
		InnerSize = EIGEN_SIZE_MIN_PREFER_FIXED(_LhsNested::ColsAtCompileTime, _RhsNested::RowsAtCompileTime),

		MaxRowsAtCompileTime = _LhsNested::MaxRowsAtCompileTime,
		MaxColsAtCompileTime = _RhsNested::MaxColsAtCompileTime,

		EvalToRowMajor = (RhsFlags & LhsFlags & RowMajorBit),
		ResultIsSkyline = ProductMode == SkylineTimeSkylineProduct,

		RemovedBits = ~((EvalToRowMajor ? 0 : RowMajorBit) | (ResultIsSkyline ? 0 : SkylineBit)),

		Flags =
			(int(LhsFlags | RhsFlags) & HereditaryBits & RemovedBits) | EvalBeforeAssigningBit | EvalBeforeNestingBit,

		CoeffReadCost = HugeCost
	};

	typedef typename internal::conditional<ResultIsSkyline,
										   SkylineMatrixBase<SkylineProduct<LhsNested, RhsNested, ProductMode>>,
										   MatrixBase<SkylineProduct<LhsNested, RhsNested, ProductMode>>>::type Base;
};

namespace internal {
template<typename LhsNested, typename RhsNested, int ProductMode>
class SkylineProduct
	: no_assignment_operator
	, public traits<SkylineProduct<LhsNested, RhsNested, ProductMode>>::Base
{
  public:
	EIGEN_GENERIC_PUBLIC_INTERFACE(SkylineProduct)

  private:
	typedef typename traits<SkylineProduct>::_LhsNested _LhsNested;
	typedef typename traits<SkylineProduct>::_RhsNested _RhsNested;

  public:
	template<typename Lhs, typename Rhs>
	EIGEN_STRONG_INLINE SkylineProduct(const Lhs& lhs, const Rhs& rhs)
		: m_lhs(lhs)
		, m_rhs(rhs)
	{
		eigen_assert(lhs.cols() == rhs.rows());

		enum
		{
			ProductIsValid = _LhsNested::ColsAtCompileTime == Dynamic || _RhsNested::RowsAtCompileTime == Dynamic ||
							 int(_LhsNested::ColsAtCompileTime) == int(_RhsNested::RowsAtCompileTime),
			AreVectors = _LhsNested::IsVectorAtCompileTime && _RhsNested::IsVectorAtCompileTime,
			SameSizes = EIGEN_PREDICATE_SAME_MATRIX_SIZE(_LhsNested, _RhsNested)
		};
		// note to the lost user:
		//    * for a dot product use: v1.dot(v2)
		//    * for a coeff-wise product use: v1.cwise()*v2
		EIGEN_STATIC_ASSERT(
			ProductIsValid || !(AreVectors && SameSizes),
			INVALID_VECTOR_VECTOR_PRODUCT__IF_YOU_WANTED_A_DOT_OR_COEFF_WISE_PRODUCT_YOU_MUST_USE_THE_EXPLICIT_FUNCTIONS)
		EIGEN_STATIC_ASSERT(
			ProductIsValid || !(SameSizes && !AreVectors),
			INVALID_MATRIX_PRODUCT__IF_YOU_WANTED_A_COEFF_WISE_PRODUCT_YOU_MUST_USE_THE_EXPLICIT_FUNCTION)
		EIGEN_STATIC_ASSERT(ProductIsValid || SameSizes, INVALID_MATRIX_PRODUCT)
	}

	EIGEN_STRONG_INLINE Index rows() const { return m_lhs.rows(); }

	EIGEN_STRONG_INLINE Index cols() const { return m_rhs.cols(); }

	EIGEN_STRONG_INLINE const _LhsNested& lhs() const { return m_lhs; }

	EIGEN_STRONG_INLINE const _RhsNested& rhs() const { return m_rhs; }

  protected:
	LhsNested m_lhs;
	RhsNested m_rhs;
};

// dense = skyline * dense
// Note that here we force no inlining and separate the setZero() because GCC messes up otherwise

template<typename Lhs, typename Rhs, typename Dest>
EIGEN_DONT_INLINE void
skyline_row_major_time_dense_product(const Lhs& lhs, const Rhs& rhs, Dest& dst)
{
	typedef typename remove_all<Lhs>::type _Lhs;
	typedef typename remove_all<Rhs>::type _Rhs;
	typedef typename traits<Lhs>::Scalar Scalar;

	enum
	{
		LhsIsRowMajor = (_Lhs::Flags & RowMajorBit) == RowMajorBit,
		LhsIsSelfAdjoint = (_Lhs::Flags & SelfAdjointBit) == SelfAdjointBit,
		ProcessFirstHalf = LhsIsSelfAdjoint && (((_Lhs::Flags & (UpperTriangularBit | LowerTriangularBit)) == 0) ||
												((_Lhs::Flags & UpperTriangularBit) && !LhsIsRowMajor) ||
												((_Lhs::Flags & LowerTriangularBit) && LhsIsRowMajor)),
		ProcessSecondHalf = LhsIsSelfAdjoint && (!ProcessFirstHalf)
	};

	// Use matrix diagonal part <- Improvement : use inner iterator on dense matrix.
	for (Index col = 0; col < rhs.cols(); col++) {
		for (Index row = 0; row < lhs.rows(); row++) {
			dst(row, col) = lhs.coeffDiag(row) * rhs(row, col);
		}
	}
	// Use matrix lower triangular part
	for (Index row = 0; row < lhs.rows(); row++) {
		typename _Lhs::InnerLowerIterator lIt(lhs, row);
		const Index stop = lIt.col() + lIt.size();
		for (Index col = 0; col < rhs.cols(); col++) {

			Index k = lIt.col();
			Scalar tmp = 0;
			while (k < stop) {
				tmp += lIt.value() * rhs(k++, col);
				++lIt;
			}
			dst(row, col) += tmp;
			lIt += -lIt.size();
		}
	}

	// Use matrix upper triangular part
	for (Index lhscol = 0; lhscol < lhs.cols(); lhscol++) {
		typename _Lhs::InnerUpperIterator uIt(lhs, lhscol);
		const Index stop = uIt.size() + uIt.row();
		for (Index rhscol = 0; rhscol < rhs.cols(); rhscol++) {

			const Scalar rhsCoeff = rhs.coeff(lhscol, rhscol);
			Index k = uIt.row();
			while (k < stop) {
				dst(k++, rhscol) += uIt.value() * rhsCoeff;
				++uIt;
			}
			uIt += -uIt.size();
		}
	}
}

template<typename Lhs, typename Rhs, typename Dest>
EIGEN_DONT_INLINE void
skyline_col_major_time_dense_product(const Lhs& lhs, const Rhs& rhs, Dest& dst)
{
	typedef typename remove_all<Lhs>::type _Lhs;
	typedef typename remove_all<Rhs>::type _Rhs;
	typedef typename traits<Lhs>::Scalar Scalar;

	enum
	{
		LhsIsRowMajor = (_Lhs::Flags & RowMajorBit) == RowMajorBit,
		LhsIsSelfAdjoint = (_Lhs::Flags & SelfAdjointBit) == SelfAdjointBit,
		ProcessFirstHalf = LhsIsSelfAdjoint && (((_Lhs::Flags & (UpperTriangularBit | LowerTriangularBit)) == 0) ||
												((_Lhs::Flags & UpperTriangularBit) && !LhsIsRowMajor) ||
												((_Lhs::Flags & LowerTriangularBit) && LhsIsRowMajor)),
		ProcessSecondHalf = LhsIsSelfAdjoint && (!ProcessFirstHalf)
	};

	// Use matrix diagonal part <- Improvement : use inner iterator on dense matrix.
	for (Index col = 0; col < rhs.cols(); col++) {
		for (Index row = 0; row < lhs.rows(); row++) {
			dst(row, col) = lhs.coeffDiag(row) * rhs(row, col);
		}
	}

	// Use matrix upper triangular part
	for (Index row = 0; row < lhs.rows(); row++) {
		typename _Lhs::InnerUpperIterator uIt(lhs, row);
		const Index stop = uIt.col() + uIt.size();
		for (Index col = 0; col < rhs.cols(); col++) {

			Index k = uIt.col();
			Scalar tmp = 0;
			while (k < stop) {
				tmp += uIt.value() * rhs(k++, col);
				++uIt;
			}

			dst(row, col) += tmp;
			uIt += -uIt.size();
		}
	}

	// Use matrix lower triangular part
	for (Index lhscol = 0; lhscol < lhs.cols(); lhscol++) {
		typename _Lhs::InnerLowerIterator lIt(lhs, lhscol);
		const Index stop = lIt.size() + lIt.row();
		for (Index rhscol = 0; rhscol < rhs.cols(); rhscol++) {

			const Scalar rhsCoeff = rhs.coeff(lhscol, rhscol);
			Index k = lIt.row();
			while (k < stop) {
				dst(k++, rhscol) += lIt.value() * rhsCoeff;
				++lIt;
			}
			lIt += -lIt.size();
		}
	}
}

template<typename Lhs, typename Rhs, typename ResultType, int LhsStorageOrder = traits<Lhs>::Flags& RowMajorBit>
struct skyline_product_selector;

template<typename Lhs, typename Rhs, typename ResultType>
struct skyline_product_selector<Lhs, Rhs, ResultType, RowMajor>
{
	typedef typename traits<typename remove_all<Lhs>::type>::Scalar Scalar;

	static void run(const Lhs& lhs, const Rhs& rhs, ResultType& res)
	{
		skyline_row_major_time_dense_product<Lhs, Rhs, ResultType>(lhs, rhs, res);
	}
};

template<typename Lhs, typename Rhs, typename ResultType>
struct skyline_product_selector<Lhs, Rhs, ResultType, ColMajor>
{
	typedef typename traits<typename remove_all<Lhs>::type>::Scalar Scalar;

	static void run(const Lhs& lhs, const Rhs& rhs, ResultType& res)
	{
		skyline_col_major_time_dense_product<Lhs, Rhs, ResultType>(lhs, rhs, res);
	}
};

} // end namespace internal

// template<typename Derived>
// template<typename Lhs, typename Rhs >
// Derived & MatrixBase<Derived>::lazyAssign(const SkylineProduct<Lhs, Rhs, SkylineTimeDenseProduct>& product) {
//     typedef typename internal::remove_all<Lhs>::type _Lhs;
//     internal::skyline_product_selector<typename internal::remove_all<Lhs>::type,
//             typename internal::remove_all<Rhs>::type,
//             Derived>::run(product.lhs(), product.rhs(), derived());
//
//     return derived();
// }

// skyline * dense

template<typename Derived>
template<typename OtherDerived>
EIGEN_STRONG_INLINE const typename SkylineProductReturnType<Derived, OtherDerived>::Type
SkylineMatrixBase<Derived>::operator*(const MatrixBase<OtherDerived>& other) const
{

	return typename SkylineProductReturnType<Derived, OtherDerived>::Type(derived(), other.derived());
}

} // end namespace Eigen

#endif // EIGEN_SKYLINEPRODUCT_H
